package com.awenhui.demo.module.samsql.utils;

import com.awenhui.demo.module.samsql.model.entity.SamSqlParamInEntity;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder;
import org.apache.ibatis.session.Configuration;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author yuxf
 * @version 1.0
 * @date 2021/2/5 9:17
 */
public class MyBatisSqlUtils {

    /**
     * 根据mybatis sql模板生成sql的功能
     * <select id="">
     * select * from ua_user
     * <where>
     * mobile like #{mobile}
     * <if test="name!=null and name != ''">
     * AND    username like '%${name}%'
     * </if>
     *
     * <if test="stateList!=null and stateList.size() > 0">
     * <foreach collection="stateList" item="item" open="AND state in (" separator="," close=")">
     * "#{item}
     * </foreach>
     * </if>
     * <foreach collection="flowList" item="item" open="AND flow in (" separator="," close=")">
     * "#{item}
     * </foreach>
     * </where>
     * </select>
     *
     * @param request
     * @param sqlTemplate  mapper配置的一段xml代码，如<select>...<select/>，支持单个变量和数组
     * @param sqlParamList 参数列表配置
     * @return
     */
    public static String getSql(HttpServletRequest request, String sqlTemplate, List<SamSqlParamInEntity> sqlParamList) {
        if (sqlTemplate == null) throw new RuntimeException("sqlTemplate is null!");
        //根据请求构建sql入参
        Map<String, Object> myBatisParam = getMyBatisParam(request, sqlParamList);
        //根据sql模板构建BoundSql

        if (!sqlTemplate.trim().startsWith("<")) {
            String sqlType = sqlTemplate.trim().split(" ")[0];
            System.out.println(Arrays.toString(sqlTemplate.split(" ")) );
            sqlTemplate=String.format("<%s>%s</%s>",sqlType,sqlTemplate,sqlType);
        }
        System.out.println("==>     "+sqlTemplate);
        XPathParser parser = new XPathParser(sqlTemplate);
        XNode xNode = parser.evalNode("select|insert|update|delete");
        XMLScriptBuilder builder = new XMLScriptBuilder(new Configuration(), xNode, null);
        SqlSource sqlSource = builder.parseScriptNode();
        BoundSql boundSql = sqlSource.getBoundSql(myBatisParam);
        //生成要执行的sql语句
        String sql = boundSql.getSql();
        if (boundSql.getParameterMappings().size() > 0) {
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() == ParameterMode.OUT) continue;
                if (sql.indexOf("?") != -1) {
                    String paramValue = getParamValue(myBatisParam, parameterMapping.getProperty(), boundSql.getAdditionalParameter(parameterMapping.getProperty()));
                    sql = sql.replaceFirst("\\?", paramValue);
                }
            }
        }
        sql = sql.replace("\n", " ").replaceAll("[ ]+", " ");//替换换行和多个空格为一个空格
        System.out.println("<==     "+sql);
        return sql;
    }


    /**
     * 处理参数的值
     *
     * @param myBatisParam 入参
     * @param propertyName 参数名
     * @param value        扩展参数
     * @return
     */
    private static String getParamValue(Map<String, Object> myBatisParam, String propertyName, Object value) {
        //先从入参中取值
        if (myBatisParam.containsKey(propertyName)) {
            value = myBatisParam.get(propertyName);
        }
        if (value == null) return "";
        if (value instanceof String) {
            return "'" + value.toString().replace("'", "''") + "'";
        } else return value.toString();
    }


    /**
     * 获取mybatis的入参
     *
     * @param request
     * @param sqlParamList
     * @return
     */
    private static Map<String, Object> getMyBatisParam(HttpServletRequest request, List<SamSqlParamInEntity> sqlParamList) {
        Map<String, Object> map = new HashMap<>();
        for (SamSqlParamInEntity sqlParam : sqlParamList) {
            String parameter;
            //获取请求中的参数值
            if (sqlParam.getIsArray()) {
                String[] parameterValues = request.getParameterValues(sqlParam.getName());
                if (parameterValues != null && parameterValues.length > 0) {
                    parameter = String.join(",", parameterValues);
                } else {
                    parameter = null;
                }
            } else {
                parameter = request.getParameter(sqlParam.getName());
            }
            //如果为空设置默认值
            if (StringUtils.isEmpty(parameter) && !StringUtils.isEmpty(sqlParam.getDefaultValue())) {
                parameter = sqlParam.getDefaultValue();
            }
            //非空检查
            if (sqlParam.getIsNull().equals(0) && StringUtils.isEmpty(parameter)) {
                throw new RuntimeException("入参[" + sqlParam.getName() + "]不允许为空");
            }
            //正则检查
            if (!StringUtils.isEmpty(sqlParam.getRegularStr())) {
                if (sqlParam.getIsArray()) {
                    String[] array = parameter.split(",");
                    for (String item : array) {
                        validRegex(sqlParam, item);
                    }
                } else {
                    validRegex(sqlParam, parameter);
                }
            }
            //设置参数
            Object mybatisValue;
            if (sqlParam.getIsArray()) {
                String[] array = parameter.split(",");
                mybatisValue = Arrays.stream(array).map(r -> convertSqlParam(sqlParam.getDataType(), r)).toArray();
            } else {
                mybatisValue = convertSqlParam(sqlParam.getDataType(), parameter);
            }
            map.put(sqlParam.getName(), mybatisValue);
        }
        return map;
    }

    /**
     * 正则检查
     *
     * @param sqlParam
     * @param value
     */
    private static void validRegex(SamSqlParamInEntity sqlParam, String value) {
        boolean matches = Pattern.matches(sqlParam.getRegularStr(), value);
        if (!matches) {
            if (!StringUtils.isEmpty(sqlParam.getErrorMsg())) {
                throw new RuntimeException(sqlParam.getErrorMsg());
            } else {
                throw new RuntimeException("参数" + sqlParam.getName() + "：" + value + "错误");
            }
        }
    }


    private static Object convertSqlParam(String dataType, String paramVal) {
        switch (dataType) {
            case "Integer":
                return Integer.valueOf(paramVal);
            case "Double":
                return Double.valueOf(paramVal);
            case "Float":
                return Float.valueOf(paramVal);
            case "Long":
                return Long.valueOf(paramVal);
            default:
                return paramVal;
        }
    }

}
